package cn.com.git.workflow.config;

import cn.com.git.common.exception.ApiException;
import cn.com.git.common.util.object.HashMapBuilder;
import cn.com.git.common.util.object.JSONUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Set;

/**
 * 全局异常处理
 */
@Controller
public class GlobalExceptionHandler extends BasicErrorController {

    private final ErrorAttributes errorAttributes;

    @Value("${spring.profiles.active}")
    private String profile;

    public GlobalExceptionHandler(ErrorAttributes errorAttributes, ServerProperties serverProperties) {
        super(errorAttributes, serverProperties.getError());
        this.errorAttributes = errorAttributes;
    }

    @Override
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        Map<String, Object> body = handlerResponseBody(request);
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        response.setStatus(HttpStatus.OK.value());
        try {
            PrintWriter printWriter = response.getWriter();
            printWriter.write(JSONUtils.toJson(body));
            printWriter.flush();
            printWriter.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = handlerResponseBody(request);
        HttpStatus status = HttpStatus.OK;
        return new ResponseEntity(body, status);
    }

    protected Map<String, Object> handlerResponseBody(HttpServletRequest request) {
        Map<String, Object> body;
        WebRequest webRequest = new ServletWebRequest(request);
        Throwable throwable = errorAttributes.getError(webRequest);
        HttpStatus status = this.getStatus(request);
        String requestUrl = this.errorAttributes.getErrorAttributes(webRequest,
                this.isIncludeStackTrace(request, MediaType.ALL))
                .getOrDefault("path", "/error").toString();

        if (status.equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
            //500服务器内部错误
            if (throwable instanceof ApiException) {
                body = buildApiErrorResult(requestUrl, (ApiException) throwable);
            } else if (throwable instanceof ConstraintViolationException) {
                ConstraintViolationException exception = (ConstraintViolationException) throwable;
                body = buildDataFormatErrorResult(requestUrl, exception);
            } else {
                body = buildServerErrorResult1(requestUrl, throwable);
            }
        } else if (status.equals(HttpStatus.BAD_REQUEST)) {
            // 400 错误的请求
            body = buildBadRequestErrorResult(requestUrl, throwable);
        } else if (status.equals(HttpStatus.NOT_FOUND)) {
            //404 资源部存在
            body = buildNotFoundErrorResult(requestUrl);
        } else {
            //其他http错误
            body = buildResultByHttpStatus(requestUrl, status, throwable);
        }
        return body;
    }


    private Map<String, Object> buildResultByHttpStatus(String requestUrl, HttpStatus status, Throwable throwable) {
        String exception = null;
        if (throwable != null) {
            exception = throwable.getClass().getName();
        }
        return HashMapBuilder.<String, Object>builder()
                .put("code", status.value())
                .put("msg", status.getReasonPhrase())
                .put("data", null)
                .put("timestamp", System.currentTimeMillis())
                .put("path", requestUrl)
                .put("exception", exception).build();
    }

    private Map<String, Object> buildNotFoundErrorResult(String requestUrl) {
        return HashMapBuilder.<String, Object>builder()
                .put("code", HttpStatus.NOT_FOUND.value())
                .put("msg", HttpStatus.NOT_FOUND.getReasonPhrase())
                .put("data", null)
                .put("path", requestUrl)
                .put("exception", null)
                .put("timestamp", System.currentTimeMillis()).build();
    }

    private Map<String, Object> buildBadRequestErrorResult(String requestUrl, Throwable throwable) {
        return HashMapBuilder.<String, Object>builder()
                .put("code", HttpStatus.BAD_REQUEST.value())
                .put("msg", HttpStatus.BAD_REQUEST.getReasonPhrase() + ":" + throwable.getMessage())
                .put("data", null)
                .put("path", requestUrl)
                .put("exception", throwable.getClass().getName())
                .put("timestamp", System.currentTimeMillis()).build();
    }

    private Map<String, Object> buildApiErrorResult(String requestUrl, ApiException error) {
        return HashMapBuilder.<String, Object>builder()
                .put("code", error.getCode())
                .put("msg", error.getMessage())
                .put("data", error.getParams())
                .put("path", requestUrl)
                .put("exception", error.getClass().getName())
                .put("timestamp", System.currentTimeMillis()).build();
    }

    private Map<String, Object> buildServerErrorResult1(String requestUrl, Throwable throwable) {
        String message;
        if ("pro".equals(profile)) {
            message = "服务器内部异常,请稍后重试！";
        } else {
            message = throwable.getMessage();
        }
        return HashMapBuilder.<String, Object>builder()
                .put("code", HttpStatus.INTERNAL_SERVER_ERROR.value())
                .put("msg", message)
                .put("data", null)
                .put("path", requestUrl)
                .put("exception", throwable.getClass().getName())
                .put("timestamp", System.currentTimeMillis()).build();
    }

    private Map<String, Object> buildDataFormatErrorResult(String requestUrl, ConstraintViolationException throwable) {
        Set<ConstraintViolation<?>> violations = throwable.getConstraintViolations();
        String message = "数据格式错误";
        for (ConstraintViolation<?> violation : violations) {
            message = violation.getMessageTemplate();
            break;
        }
        return HashMapBuilder.<String, Object>builder()
                .put("code", HttpStatus.INTERNAL_SERVER_ERROR.value())
                .put("msg", message)
                .put("data", null)
                .put("path", requestUrl)
                .put("exception", throwable.getClass().getName())
                .put("timestamp", System.currentTimeMillis()).build();
    }
}
